package network;

import gamemngr.GameMngr;
import gui.NetworkGameDialog;
import gui.RiskGUI;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack;
import java.util.concurrent.ArrayBlockingQueue;

import logic.Player;

public class Client implements Runnable{

	private static String serverIP = "10.0.0.9";
	private static Socket socket;
	private static ObjectInputStream ois;
	private static ObjectOutputStream oos;
	private static String name;
	private static RiskGUI gui;
	private static NetworkGameDialog dialog;
	public static final Scanner scanner = new Scanner(System.in);
	private static boolean isConsole = false;
	
	private static Queue<NetworkMsg> clientMsgs = new ArrayBlockingQueue<NetworkMsg>(100);

	public static void closeConnection(){
		try {
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		isConsole = true;
		getPlayerName();
		getServerIP();
		socket = null;
		startClient();
	}

	public static void setDialog(NetworkGameDialog dialog) {
		Client.dialog = dialog;
	}

	public static void setServerIP(String serverIP) {
		Client.serverIP = serverIP;
	}
	
	public static boolean connect(){
		try {
			socket = new Socket(serverIP,Server.serverPort);
		} catch (Exception e) {
			return false;
		}
		return true;
	}

	public static void startClientWithoutConnect(){
		handleMsgs();
		if(isConsole){
			try {
				oos.close();
				ois.close();
				socket.close();
			} catch (Exception e) {}
		}
	}
	
	/**
	 * 
	 */
	public static void startClient() {
		connect();
		startClientWithoutConnect();
	}

	private static void getPlayerName() {
		System.out.println("Enter your name:");
		name = scanner.next();
	}

	private static void getServerIP() {
		System.out.println("Enter server IP address:");
		serverIP = scanner.next();
	}

	private static boolean isNetworkGUI(){
		return (dialog != null);
	}

	/**
	 * @param ois
	 */
	public static void handleMsgs() {
		NetworkGame newGame = null;
		ClientConsole console = null;
		System.out.println("Choose the number of the game you want to join, or -1 to create a new game:");
		while(true){
			NetworkMsg msg = getMsg();
			if(msg == null){// the client or server disconnected
				return;
			}
			//dispatching messages
			switch(msg.getType()){
			case OpenedGames:
				if(isNetworkGUI()){
					NetworkGameDialog.lock.lock();
					try{
						NetworkGameDialog.games = msg.getOpendGames();
						NetworkGameDialog.needToUpdate = true;
					} finally {
						NetworkGameDialog.lock.unlock();
					}
				}
				if(msg.getOpendGames().keySet().size() == 0){
					System.out.println("There are no opened games.");
				}
				for(int index : msg.getOpendGames().keySet()) {
					NetworkGame game = msg.getOpendGames().get(index);
					System.out.print(index + ". ");
					for(NetworkPlayer player : game.getPlayers()){
						System.out.print(player.getName() + ", ");
					}
					System.out.println();
				}
				System.out.println();
				if(!isNetworkGUI()){
					if(console == null || !console.isAlive()){
						console = new ClientConsole();
						console.setOpendGames(msg.getOpendGames());
						console.start();
					} else {
						console.setOpendGames(msg.getOpendGames());
					}
				}
				break;
			case Info:
				InfoMsg m = (InfoMsg)msg;
				System.out.println(m.getInfo());
				break;
			case DBInfo:
				Client.addClientMsg(msg);
				break;
			case Error:
				Client.addClientMsg(msg);
				break;
			case UserProperties:
				Client.addClientMsg(msg);
				break;
			case GameStarted:
				GameStarted startedMsg = (GameStarted)msg;
				newGame = startedMsg.getNetworkGame();
				newGame.setGame(startedMsg.getGame());

				System.out.println("me index:"+startedMsg.getMeIndex());
				for(int i=0; i<newGame.getGame().getPlayers().size(); i++){
					newGame.getGame().getPlayers().get(i).setColour(RiskGUI.playerColours[i]);
					newGame.getGame().getPlayers().get(i).setGame(startedMsg.getGame());
					if(i==startedMsg.getMeIndex()){
						newGame.getGame().getPlayers().get(i).setMe(true);
					}
				}
				if(gui == null){
					gui = new RiskGUI(newGame.getGame());
					gui.addToLog.compareAndSet(null, newGame.getGame().getPlayers().get(0) + ":");
					new ClientThread(gui, startedMsg.getMeIndex()).start();
					newGame.getGame().setReenforcement(newGame.getGame().getCurrentPlayer());
					gui.startGUI();
				}
				else{
					gui.setGame(newGame.getGame());
					gui.setNetwork(true);
					gui.newGameMngr.set(newGame.getGame());
					gui.updateGUI.compareAndSet(false, true);	
					gui.addToLog.compareAndSet(null, newGame.getGame().getPlayers().get(0) + ":");
					new ClientThread(gui, startedMsg.getMeIndex()).start();
					newGame.getGame().setReenforcement(newGame.getGame().getCurrentPlayer());
					RiskGUI.doCloseNetworkDialog = true;
					RiskGUI.display.wake();
				}
				return;
			}
		}
	}

	public static void setGui(RiskGUI gui) {
		Client.gui = gui;
	}

	public static int getWantedPlayersNum(){
		System.out.println("Enter number of wanted players (between 2 to 6):");
		Scanner s = new Scanner(System.in);
		while(true){
			int result = s.nextInt();
			if(result < 2 || result > 6){
				System.out.println("Wrong input. Input must be between 2 to 6. Try again:");
			} else {
				return result;
			}
		}
	}

	public static boolean sendMsg(NetworkMsg msg){
		try {
			oos = new ObjectOutputStream(socket.getOutputStream());
			oos.writeObject(msg);
		} catch (IOException e) {
			return false;
		}
		return true;
	}

	/**
	 * @return the message
	 */
	public static NetworkMsg getMsg() {
		NetworkMsg msg = null;
		try {
			ois = new ObjectInputStream(socket.getInputStream());
			msg = (NetworkMsg)ois.readObject();
		} catch (IOException e) {} 
		catch (ClassNotFoundException e) {}
		return msg;
	}

	/**
	 * @param name the name to set
	 */
	public static void setName(String name) {
		Client.name = name;
	}

	/**
	 * @return the name
	 */
	public static String getName() {
		return name;
	}

	public void run(){
		Client.startClientWithoutConnect();
	}


	public static void addClientMsg(NetworkMsg clientMsg) {
		clientMsgs.add(clientMsg);
	}
 
	public static NetworkMsg getClientMsg() {
		NetworkMsg clientMsg = null;
		int count = 0;
		while (true) {
			if (clientMsgs.peek()==null) {
				try {
					Thread.sleep(500);
					count++;
					if (count>5) {
						clientMsg = new ErrorMsg("No response from the server.");
						return clientMsg;
					}
				} catch (InterruptedException e) {
					clientMsg = new ErrorMsg("No response from the server.");
				}
			} else {
				clientMsg = clientMsgs.poll();
				break;
			}
		}
		return clientMsg;
	}
	
}

class ClientConsole extends Thread{

	private Map<Integer, NetworkGame> opendGames;
	private int choise = -2;

	/**
	 * Wait for input from the client
	 */
	public void run(){
		while(true){
			int bytes = 0;
			try {
				bytes = System.in.available();
			} catch (IOException e1) {}
			if(bytes != 0){
				int result = Client.scanner.nextInt();
				if(result == -1){
					choise = -1;
					break;
				}
				else {
					if(opendGames.keySet().contains(result)){
						choise = result;
						break;
					} else {
						System.out.println("Wrong input. Try Again:");
					}
				}
			} else{
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {}
			}
		}
		if(choise == -1){
			Client.sendMsg(new OpenGameMsg(Client.getName(), Client.getWantedPlayersNum()));
		} else {
			Client.sendMsg(new JoinGameMsg(choise, Client.getName()));
		}
	}

	/**
	 * @param opendGames the opendGames to set
	 */
	public void setOpendGames(Map<Integer, NetworkGame> opendGames) {
		this.opendGames = opendGames;
	}
}
